﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Opennav.MapGenerator.Map.Labels;
using System.IO;
using System.Diagnostics;
using Opennav.MapGenerator.Locator;

namespace Opennav.MapGenerator.Map.Locations
{
    public class LocationSubfile
    {
        private LabelSubfile _labelFile;
        private List<Country> _countries = new List<Country>();
        private List<Region> _regions = new List<Region>();
        private List<City> _cities = new List<City>();
        private Dictionary<GeoArea, Country> _countryMap = new Dictionary<GeoArea, Country>();
        private Dictionary<GeoArea, Region> _regionMap = new Dictionary<GeoArea, Region>();
        private Dictionary<GeoPlace, City> _cityMap = new Dictionary<GeoPlace, City>();
        private int _offset = -1;

        public LocationSubfile(LabelSubfile labelFile)
        {
            this._labelFile = labelFile;
        }

        public int Offset
        {
            get
            {
                Debug.Assert(_offset >= 0);
                return _offset;
            }
            set
            {
                this._offset = value;
            }
        }

        public IList<City> Cities { get { return _cities; } }

        public IList<Country> Countries { get { return _countries; } }

        public City CreateCity(GeoPlace place)
        {
            if (place == null) return null;
            if (place.ParentArea == null && place.Parent == null) return null;
            City city = null;
            if (!_cityMap.TryGetValue(place, out city))
            {
                if (place.Parent != null)
                {
                    Region region = CreateRegion(place.ParentArea);
                    City parentCity = CreateCity(place.Parent);
                    if (parentCity == null) return null;
                    if (region == null) return null;
                    Label label = _labelFile.Create(place.Name);
                    city = new City(place, parentCity, region, label);
                }
                else
                {
                    Region region = CreateRegion(place.ParentArea);
                    if (region == null) return null;
                    Label label = _labelFile.Create(place.Name);
                    city = new City(place, region, label);
                }
                _cities.Add(city);
                _cityMap[place] = city;
            }
            return city;
        }

        public Region CreateRegion(GeoArea area)
        {
            if (area == null) return null;
            if (area.Level <= 4) return null;
            if (area.Parent == null) return null;
            Region region = null;
            if (!_regionMap.TryGetValue(area, out region))
            {
                if (area.Parent.Level == 4)
                {
                    Country country = CreateCountry(area.Parent);
                    if (country == null) return null;
                    Label label = _labelFile.Create(area.Name);
                    region = new Region((short)_regions.Count, area, country, label);
                }
                else
                {
                    Region parentRegion = CreateRegion(area.Parent);
                    if (parentRegion == null) return null;
                    Label label = _labelFile.Create(area.Name);
                    region = new Region((short)_regions.Count, area, parentRegion, label);
                }
                _regions.Add(region);
                _regionMap[area] = region;
            }
            return region;
        }

        public Country CreateCountry(GeoArea area)
        {
            if (area.Level != 4) return null;
            Country country = null;
            if (!_countryMap.TryGetValue(area, out country))
            {
                Label label = _labelFile.Create(area.Name);
                country = new Country((byte)_countries.Count, area, label);
                _countries.Add(country);
                _countryMap[area] = country;
            }
            return country;
        }

        private int _cityTableOffset = -1;

        public void Write1(Stream stream)
        {
            _cities.Sort(new GeographicalCityComparer());
            for (int id = 0; id < _cities.Count; id++)
                _cities[id].Id = id;

            BinaryWriter writer = new BinaryWriter(stream);
            int countryTableOffset = 0;
            int regionTableOffset = 0;
            int cityTableOffset = 0;
            int offset = (int)stream.Position;

            writer.Write(countryTableOffset);
            writer.Write((byte)_countries.Count);
            writer.Write(regionTableOffset);
            writer.Write((short)_regions.Count);
            writer.Write(cityTableOffset);
            writer.Write((byte)(_cities.Count & 0xff));
            writer.Write((ushort)(_cities.Count >> 8));

            countryTableOffset = (int)stream.Position - offset;
            foreach (Country country in _countries)
            {
                country.Write(stream);
            }
            regionTableOffset = (int)stream.Position - offset;
            foreach (Region region in _regions)
            {
                region.Write(stream);
            }
            cityTableOffset = (int)stream.Position - offset;
            _cityTableOffset = (int)stream.Position;
            foreach (City city in _cities)
            {
                city.Write1(stream);
            }
            int endOffset = (int)stream.Position;
            stream.Seek(offset, SeekOrigin.Begin);
            writer.Write(countryTableOffset);
            writer.Write((byte)_countries.Count);
            writer.Write(regionTableOffset);
            writer.Write((short)_regions.Count);
            writer.Write(cityTableOffset);
            writer.Write((byte)(_cities.Count & 0xff));
            writer.Write((ushort)(_cities.Count >> 8));
            stream.Seek(endOffset, SeekOrigin.Begin);
        }

        public void Write2(Stream stream)
        {
            Debug.Assert(_cityTableOffset > 0);
            stream.Seek(_cityTableOffset, SeekOrigin.Begin);
            foreach (City city in _cities)
            {
                city.Write2(stream);
            }
        }

        private class GeographicalCityComparer : IComparer<City>
        {
            public int Compare(City x, City y)
            {
                Stack<City> parentsx = new Stack<City>();
                do
                {
                    parentsx.Push(x);
                    x = x.ParentCity;
                } 
                while (x != null);
                Stack<City> parentsy = new Stack<City>();
                do
                {
                    parentsy.Push(y);
                    y = y.ParentCity;
                }
                while (y != null);
                City ax = null, ay = null;
                while (parentsx.Count > 0 && parentsy.Count > 0 && (ax = parentsx.Pop()) == (ay = parentsy.Pop())) { }
                if (ax == ay)
                {
                    if (parentsx.Count > 0)
                        return 1;
                    else if (parentsy.Count > 0)
                        return -1;
                    else
                        return 0;
                }
                else
                {
                    return ax.Label.CompareTo(ay.Label);
                }
            }
        }
    }
}
